#pragma once

#include <angelscript.h>
#include <boost/function.hpp>
#include <boost/unordered_map.hpp>
#include <boostext/iostreams/bin_ostream.hpp>
#include <engine.core/System.hpp>

#include "exports.hpp"

// Module & Building Modules
#include "Module.hpp"
#include "Builder.hpp"

// Script Object & Context
#include "Object.hpp"
#include "ContextPool.hpp"

// Reflection
#include "Enum.hpp"
#include "Type.hpp"
#include "Function.hpp"
#include "Variable.hpp"

// Debugging
#include "Debugger.hpp"




namespace Engine
{
	namespace Script
	{
		class ENGINE_SCRIPT_API System
			: public ::Engine::System
			, public Reflectable
		{
		public:

			SHARED(System);

			typedef boost::function<void(logger::entry)>   LogCallback;

		protected:

			/** 
			 * Creates a script system.
			 * Instead of adding errors/infos/warnings/etc to the logging system, they
			 * will be handed to the given callback instead.
			 */
			System(LogCallback logCallback);
			System(const EngineInfo& info);
			virtual void post_create(ptr _this);

		public:

			MAKE_SHARED1(System, LogCallback);
			MAKE_SHARED1(System, EngineInfo);



			virtual QString name() const;
			virtual void update();



			/**
			 * Compares this engine's state against the one, stored
			 * in the given stream.
			 *
			 * @throws an exception with a detailed message about the state mismatch
			 */
			void compareState(boostext::iostreams::bin_istream& stream);

			/**
			 * Serializes this engine's state into the given stream.
			 *
			 * This function is usefull to verify that two distinct Script::System instances share the exact same
			 * state. This function does not save the values of global variables, merely the engine's properties,
			 * all registered functions and objects.
			 */
			void saveState(boostext::iostreams::bin_ostream& stream);



			/**
			 * Attaches the given debugger (if any) to the system.
			 * Detaches the old debugger (if any) as well.
			 */
			void attach(Debugger::ptr debugger);



			/**
			 * @returns a Builder for building modules
			 */
			Builder builder() const;



			/**
			 * Creates a ScriptModule for that file. Loads the contents of that file, scans for using directives
			 * and builds a module consisting of the given file and all files, required due to those directives.
			 *
			 * Either way, a valid ScriptModule is returned, or an exception is thrown.
			 */
			Module::ptr moduleFromFile(QString name, const boost::filesystem::path& path);

			/**
			 * TODO: Description
			 */
			Module::ptr moduleFromString(QString name, QString code);

			/**
			 * Creates a script module from the given bytecode.
			 * May fail horribly when the bytecode is corrupted.
			 */
			Module::ptr moduleFromBytecode(QString name, const std::vector<char>& bytecode);



			/**
			 * @returns the contextpool that can be used to retrieve a usable script context
			 */
			ContextPool::ptr contextPool() const;



			void registerGlobalProperty(const char* decl, void* pointer);

			void registerInterface(const char* name);
			void registerInterfaceMethod(const char* name, const char* decl);

			void registerStringFactory(const char* name, const asSFuncPtr& funcPtr, asDWORD callConv);
			void registerObjectType(const char* name, int size, asDWORD flags);
			void registerObjectMethod(const char* name, const char* decl, const asSFuncPtr& funcPtr, asDWORD callConv);
			void registerObjectBehaviour(const char* name, asEBehaviours behaviour, const char* decl, const asSFuncPtr& funcPtr, asDWORD callConv);
			void registerObjectProperty(const char* obj, const char* decl, int byteOffset);



			/**
			 * The system reflects over all user registered types.
			 * The internal cache is updated to accomodate for any changes.
			 */
			void reflect();

			/**
			 * @returns the number of object types
			 */
			int typeCount() const;

			/**
			 * @tests if there is a type with the given id
			 */
			bool hasType(int id) const;

			/**
			 * @returns the type with the given id
			 * @throws when there is no such type
			 */
			const Type& type(int id) const;

			/**
			 * @returns the function with the given id
			 * @throws when there is no function with the given id
			 */
			virtual Function function(int id) const;

			/**
			 * @returns all object types
			 */
			virtual std::vector<Type> types() const;

		private:

			void addPOD(asETypeIdFlags type);
			void callback(const asSMessageInfo *msg);

		private:

			typedef boost::unordered_map<int, Type>       TypeMap;
			typedef boost::unordered_map<int, Function>   FunctionMap;
			typedef boost::unordered_map<int, Enum>       EnumMap;

			weak_ptr                   m_this;
			logger::stream::ptr        m_logger;

			LogCallback                m_logCallback;

			pointer<asIScriptEngine>   m_engine;
			ContextPool::ptr           m_contextPool;

			TypeMap                    m_types;
			FunctionMap                m_functions;
			EnumMap                    m_enums;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
